﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DTO;
using DAL;
using BLL.Settings;
using System.IO;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Web;

namespace BLL.Pictures
{
    public class Picture:BasePictures
    {
        private static readonly object s_lock = new object();

        #region "Constructors"
        public Picture() { }

        public Picture(int id, byte[] pictureBinary, string mimeType, string seoFilename, bool isNew)
        {
            this.ID = id;
            this.PictureBinary = pictureBinary;
            this.MimeType = mimeType;
            this.SeoFilename = seoFilename;
            this.IsNew = isNew;
        }
        #endregion

        #region "Public Properties"

        public byte[] PictureBinary { get; set; }

        public string MimeType { get; set; }

        public string SeoFilename { get; set; }

        public bool IsNew { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether the images should be stored in data base.
        /// </summary>
        public bool StoreInDb
        {
            get
            {
                return Convert.ToBoolean(Setting.GetSettingValue("StoreInDB"));
            }
            set
            {
                //check whether it's a new value
                if (this.StoreInDb != value)
                {
                    //save the new setting value
                    Setting.UpdateSetting("StoreInDB", Convert.ToString(value));
                    //_settingService.SetSetting<bool>("Media.Images.StoreInDB", value);

                    //update all picture objects
                    List<Picture> pictures = ListPictures(0, int.MaxValue);
                    foreach (Picture picture in pictures)
                    {
                        var pictureBinary = LoadPictureBinary(picture, !value);

                        //delete from file system
                        if (value)
                            DeletePictureOnFileSystem(picture);

                        //just update a picture (all required logic is in UpdatePicture method)
                        Picture.UpdatePicture(picture.ID, picture.PictureBinary, picture.MimeType, picture.SeoFilename, true);
                        //we do not validate picture binary here to ensure that no exception ("Parameter is not valid") will be thrown when "moving" pictures
                    }
                }
            }
        }

        #endregion

        #region Utilities

        /// <summary>
        /// Calculates picture dimensions whilst maintaining aspect
        /// </summary>
        /// <param name="originalSize">The original picture size</param>
        /// <param name="targetSize">The target picture size (longest side)</param>
        /// <returns></returns>
        protected static Size CalculateDimensions(Size originalSize, int targetSize)
        {
            var newSize = new Size();
            if (originalSize.Height > originalSize.Width) // portrait 
            {
                newSize.Width = (int)(originalSize.Width * (float)(targetSize / (float)originalSize.Height));
                newSize.Height = targetSize;
            }
            else // landscape or square
            {
                newSize.Height = (int)(originalSize.Height * (float)(targetSize / (float)originalSize.Width));
                newSize.Width = targetSize;
            }
            return newSize;
        }

        /// <summary>
        /// Returns the first ImageCodecInfo instance with the specified mime type.
        /// </summary>
        /// <param name="mimeType">Mime type</param>
        /// <returns>ImageCodecInfo</returns>
        protected static ImageCodecInfo GetImageCodecInfoFromMimeType(string mimeType)
        {
            var info = ImageCodecInfo.GetImageEncoders();
            foreach (var ici in info)
                if (ici.MimeType.Equals(mimeType, StringComparison.OrdinalIgnoreCase))
                    return ici;
            return null;
        }

        /// <summary>
        /// Save picture on file system
        /// </summary>
        /// <param name="pictureId">Picture identifier</param>
        /// <param name="pictureBinary">Picture binary</param>
        /// <param name="mimeType">MIME type</param>
        protected void SavePictureInFile(int pictureId, byte[] pictureBinary, string mimeType)
        {
            string lastPart = GetFileExtensionFromMimeType(mimeType);
            string fileName = string.Format("{0}_0.{1}", pictureId.ToString("0000000"), lastPart);
            File.WriteAllBytes(GetPictureLocalPath(fileName), pictureBinary);
        }

        /// <summary>
        /// Returns the first ImageCodecInfo instance with the specified extension.
        /// </summary>
        /// <param name="fileExt">File extension</param>
        /// <returns>ImageCodecInfo</returns>
        protected static ImageCodecInfo GetImageCodecInfoFromExtension(string fileExt)
        {
            fileExt = fileExt.TrimStart(".".ToCharArray()).ToLower().Trim();
            switch (fileExt)
            {
                case "jpg":
                case "jpeg":
                    return GetImageCodecInfoFromMimeType("image/jpeg");
                case "png":
                    return GetImageCodecInfoFromMimeType("image/png");
                case "gif":
                    //use png codec for gif to preserve transparency
                    //return GetImageCodecInfoFromMimeType("image/gif");
                    return GetImageCodecInfoFromMimeType("image/png");
                default:
                    return GetImageCodecInfoFromMimeType("image/jpeg");
            }
        }

        /// <summary>
        /// Delete picture thumbs
        /// </summary>
        /// <param name="picture">Picture</param>
        protected static void DeletePictureThumbs(Picture picture)
        {
            string filter = string.Format("{0}*.*", picture.ID.ToString("0000000"));
            var thumbDirectoryPath = Globals.PhysicalApplicationPath + "content/images/thumbs";
            string[] currentFiles = System.IO.Directory.GetFiles(thumbDirectoryPath, filter, SearchOption.AllDirectories);
            foreach (string currentFileName in currentFiles)
            {
                var thumbFilePath = GetThumbLocalPath(currentFileName);
                File.Delete(thumbFilePath);
            }
        }

        /// <summary>
        /// Get picture (thumb) local path
        /// </summary>
        /// <param name="thumbFileName">Filename</param>
        /// <returns>Local picture thumb path</returns>
        protected static string GetThumbLocalPath(string thumbFileName)
        {
            //var thumbsDirectoryPath = _webHelper.MapPath("~/content/images/thumbs");
            //if (_mediaSettings.MultipleThumbDirectories)
            //{
            //    //get the first two letters of the file name
            //    var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(thumbFileName);
            //    if (fileNameWithoutExtension != null && fileNameWithoutExtension.Length > MULTIPLE_THUMB_DIRECTORIES_LENGTH)
            //    {
            //        var subDirectoryName = fileNameWithoutExtension.Substring(0, MULTIPLE_THUMB_DIRECTORIES_LENGTH);
            //        thumbsDirectoryPath = Path.Combine(thumbsDirectoryPath, subDirectoryName);
            //        if (!System.IO.Directory.Exists(thumbsDirectoryPath))
            //        {
            //            System.IO.Directory.CreateDirectory(thumbsDirectoryPath);
            //        }
            //    }
            //}
            //var thumbFilePath = Path.Combine(thumbsDirectoryPath, thumbFileName);
            //return thumbFilePath;

            var thumbsDirectoryPath = Globals.PhysicalApplicationPath + "content/images/thumbs";
            var thumbFilePath = Path.Combine(thumbsDirectoryPath, thumbFileName);
            return thumbFilePath;
        }

        /// <summary>
        /// Delete a picture on file system
        /// </summary>
        /// <param name="picture">Picture</param>
        protected static void DeletePictureOnFileSystem(Picture picture)
        {
            if (picture == null)
                throw new ArgumentNullException("picture");

            string lastPart = GetFileExtensionFromMimeType(picture.MimeType);
            string fileName = string.Format("{0}_0.{1}", picture.ID.ToString("0000000"), lastPart);
            string filePath = GetPictureLocalPath(fileName);
            if (File.Exists(filePath))
            {
                File.Delete(filePath);
            }
        }

        /// <summary>
        /// Returns the file extension from mime type.
        /// </summary>
        /// <param name="mimeType">Mime type</param>
        /// <returns>File extension</returns>
        protected static string GetFileExtensionFromMimeType(string mimeType)
        {
            if (mimeType == null)
                return null;

            //also see System.Web.MimeMapping for more mime types

            string[] parts = mimeType.Split('/');
            string lastPart = parts[parts.Length - 1];
            switch (lastPart)
            {
                case "pjpeg":
                    lastPart = "jpg";
                    break;
                case "x-png":
                    lastPart = "png";
                    break;
                case "x-icon":
                    lastPart = "ico";
                    break;
            }
            return lastPart;
        }

        /// <summary>
        /// Get picture (thumb) URL 
        /// </summary>
        /// <param name="thumbFileName">Filename</param>
        /// <param name="storeLocation">Store location URL; null to use determine the current store location automatically</param>
        /// <returns>Local picture thumb path</returns>
        protected static string GetThumbUrl(string thumbFileName, string storeLocation = null)
        {
            storeLocation = !String.IsNullOrEmpty(storeLocation)
                                    ? storeLocation
                                    : Globals.ServerHostName;
            var url = storeLocation + "content/images/thumbs/";
            url = url + thumbFileName;
            return url;
        }

        /// <summary>
        /// Get picture local path. Used when images stored on file system (not in the database)
        /// </summary>
        /// <param name="fileName">Filename</param>
        /// <returns>Local picture path</returns>
        protected static string GetPictureLocalPath(string fileName)
        {
            var imagesDirectoryPath = Globals.PhysicalApplicationPath + "content/images/";
            var filePath = Path.Combine(imagesDirectoryPath, fileName);
            return filePath;
        }

        /// <summary>
        /// Loads a picture from file
        /// </summary>
        /// <param name="pictureId">Picture identifier</param>
        /// <param name="mimeType">MIME type</param>
        /// <returns>Picture binary</returns>
        protected static byte[] LoadPictureFromFile(int pictureId, string mimeType)
        {
            string lastPart = GetFileExtensionFromMimeType(mimeType);
            string fileName = string.Format("{0}_0.{1}", pictureId.ToString("0000000"), lastPart);
            var filePath = GetPictureLocalPath(fileName);
            if (!File.Exists(filePath))
                return new byte[0];
            return File.ReadAllBytes(filePath);
        }

        /// <summary>
        /// Gets the loaded picture binary depending on picture storage settings
        /// </summary>
        /// <param name="picture">Picture</param>
        /// <param name="fromDb">Load from database; otherwise, from file system</param>
        /// <returns>Picture binary</returns>
        protected static byte[] LoadPictureBinary(Picture picture, bool fromDb)
        {
            if (picture == null)
                throw new ArgumentNullException("picture");

            byte[] result = null;
            if (fromDb)
                result = picture.PictureBinary;
            else
                result = LoadPictureFromFile(picture.ID, picture.MimeType);
            return result;
        }

        #endregion

        #region Get Picture From PicturesDetail
        private static Picture GetPictureFromPicturesDetail(PictureDetails record)
        {
            if (record == null)
                return null;
            else
                return new Picture(record.Id, record.PictureBinary, record.MimeType, record.SeoFilename, record.IsNew);
        }

        private static List<Picture> GetPictureFromListPictureDetail(List<PictureDetails> recordset)
        {
            List<Picture> picture = new List<Picture>();
            foreach (PictureDetails record in recordset)
                picture.Add(GetPictureFromPicturesDetail(record));
            return picture;
        }
        #endregion

        #region Method
        public static Picture GetPictureById(int id)
        {
            Picture picture = null;
            string key = "Picture_" + id.ToString();
            if (BasePictures.Settings.EnableCaching && BizObject.Cache[key] != null)
                picture = (Picture)BizObject.Cache[key];
            else
            {
                picture = GetPictureFromPicturesDetail(SiteProvider.Media.PictureGetById(id));
                BasePictures.CacheData(key, picture);
            }
            return picture;
        }

        public static bool DeletePicture(int id)
        {
            if (id == 0)
                return false;

            Picture picture = null;
            picture = Picture.GetPictureById(id);

            //delete thumbs
            DeletePictureThumbs(picture);

            //delete from file system
            //if (!this.StoreInDb)
            //    DeletePictureOnFileSystem(picture);

            //delete picture in datebase
            bool ret = SiteProvider.Media.PictureDelete(id);
            BizObject.PurgeCacheItems("Picture");
            return ret;
        }

        public static List<Picture> ListPictures(int maximumRows, int startRowIndex)
        {
            List<Picture> pictures = null;
            string key = "Pictures_" + startRowIndex.ToString() + "_" + maximumRows.ToString();

            if (Settings.EnableCaching && BizObject.Cache[key] != null)
            {
                pictures = (List<Picture>)BizObject.Cache[key];
            }
            else
            {
                List<PictureDetails> recordset = SiteProvider.Media.PictureList(GetPageIndex(startRowIndex, maximumRows), maximumRows);
                pictures = GetPictureFromListPictureDetail(recordset);
                BasePictures.CacheData(key, pictures);
            }
            return pictures;
        }

        public static List<Picture> GetPicturesByProductId(int productId)
        {
            List<Picture> pictures = null;
            string key = "PicturesGetByProduct_" + productId.ToString();

            if (Settings.EnableCaching && BizObject.Cache[key] != null)
            {
                pictures = (List<Picture>)BizObject.Cache[key];
            }
            else
            {
                List<PictureDetails> recordset = SiteProvider.Media.ProductListByProductId(productId);
                pictures = GetPictureFromListPictureDetail(recordset);
                BasePictures.CacheData(key, pictures);
            }
            return pictures;
        }

        public static int InsertPicture(byte[] pictureBinary, string mimeType, string seoFilename, bool isNew)
        {
            PictureDetails record = new PictureDetails(0, pictureBinary, mimeType, seoFilename, isNew);
            int ret = SiteProvider.Media.PictureInsert(record);
            BizObject.PurgeCacheItems("Picture");
            
            //if (! StoreInDb && ret != 0)
            //    SavePictureInFile(ret, pictureBinary, mimeType);
            
            return ret;
        }

        public static bool UpdatePicture(int id, byte[] pictureBinary, string mimeType, string seoFilename, bool isNew)
        {
            //delete old thumbs if a picture has been changed
            Picture picture = Picture.GetPictureById(id);
            if (seoFilename != picture.SeoFilename)
                DeletePictureThumbs(picture);

            PictureDetails record = new PictureDetails(0, pictureBinary, mimeType, seoFilename, isNew);
            bool ret = SiteProvider.Media.PictureUpdate(record);
            BizObject.PurgeCacheItems("Picture");

            //if (!this.StoreInDb && ret)
            //    SavePictureInFile(id, pictureBinary, mimeType);
            return ret;
        }

        #endregion

        #region Getting picture local path/URL methods

        /// <summary>
        /// Gets the loaded picture binary depending on picture storage settings
        /// </summary>
        /// <param name="picture">Picture</param>
        /// <returns>Picture binary</returns>
        public static byte[] LoadPictureBinary(Picture picture)
        {
            return LoadPictureBinary(picture, true);
        }

        /// <summary>
        /// Get picture SEO friendly name
        /// </summary>
        /// <param name="name">Name</param>
        /// <returns>Result</returns>
        public static string GetPictureSeName(string name)
        {
            return Helpers.GetSeName(name, true, false);
        }

        /// <summary>
        /// Gets the default picture URL
        /// </summary>
        /// <param name="targetSize">The target picture size (longest side)</param>
        /// <param name="defaultPictureType">Default picture type</param>
        /// <param name="storeLocation">Store location URL; null to use determine the current store location automatically</param>
        /// <returns>Picture URL</returns>
        public static string GetDefaultPictureUrl() //Edit
        {
            return "DefaultPictureUrl_Vutdl";
        }

        /// <summary>
        /// Get a picture URL
        /// </summary>
        /// <param name="pictureId">Picture identifier</param>
        /// <param name="targetSize">The target picture size (longest side)</param>
        /// <param name="showDefaultPicture">A value indicating whether the default picture is shown</param>
        /// <param name="storeLocation">Store location URL; null to use determine the current store location automatically</param>
        /// <param name="defaultPictureType">Default picture type</param>
        /// <returns>Picture URL</returns>
        public static string GetPictureUrl(int pictureId,
            int targetSize = 0,
            bool showDefaultPicture = true,
            string storeLocation = null)
        {
            var picture = Picture.GetPictureById(pictureId);
            return GetPictureUrl(picture, targetSize, showDefaultPicture, storeLocation);
        }

        /// <summary>
        /// Get a picture URL
        /// </summary>
        /// <param name="picture">Picture instance</param>
        /// <param name="targetSize">The target picture size (longest side)</param>
        /// <param name="showDefaultPicture">A value indicating whether the default picture is shown</param>
        /// <param name="storeLocation">Store location URL; null to use determine the current store location automatically</param>
        /// <param name="defaultPictureType">Default picture type</param>
        /// <returns>Picture URL</returns>
        public static string GetPictureUrl(Picture picture,
            int targetSize = 0,
            bool showDefaultPicture = true,
            string storeLocation = null)
        {
            string url = string.Empty;
            byte[] pictureBinary = null;
            if (picture != null)
                pictureBinary = LoadPictureBinary(picture);
            if (picture == null || pictureBinary == null || pictureBinary.Length == 0)
            {
                if (showDefaultPicture)
                {
                    url = GetDefaultPictureUrl();
                }
                return url;
            }

            string lastPart = GetFileExtensionFromMimeType(picture.MimeType);
            string thumbFileName;
            if (picture.IsNew)
            {
                DeletePictureThumbs(picture);

                //we do not validate picture binary here to ensure that no exception ("Parameter is not valid") will be thrown
                Picture.UpdatePicture(picture.ID, picture.PictureBinary, picture.MimeType, picture.SeoFilename, false);
            }
            lock (s_lock)
            {
                string seoFileName = picture.SeoFilename; // = GetPictureSeName(picture.SeoFilename); //just for sure
                if (targetSize == 0)
                {
                    thumbFileName = !String.IsNullOrEmpty(seoFileName) ?
                        string.Format("{0}_{1}.{2}", picture.ID.ToString("0000000"), seoFileName, lastPart) :
                        string.Format("{0}.{1}", picture.ID.ToString("0000000"), lastPart);
                    var thumbFilePath = GetThumbLocalPath(thumbFileName);
                    if (!File.Exists(thumbFilePath))
                    {
                        File.WriteAllBytes(thumbFilePath, pictureBinary);
                    }
                }
                else
                {
                    thumbFileName = !String.IsNullOrEmpty(seoFileName) ?
                        string.Format("{0}_{1}_{2}.{3}", picture.ID.ToString("0000000"), seoFileName, targetSize, lastPart) :
                        string.Format("{0}_{1}.{2}", picture.ID.ToString("0000000"), targetSize, lastPart);
                    var thumbFilePath = GetThumbLocalPath(thumbFileName);
                    if (!File.Exists(thumbFilePath))
                    {
                        using (Stream stream = new MemoryStream(pictureBinary))
                        {
                            Bitmap b = null;
                            try
                            {
                                //try-catch to ensure that picture binary is really OK. Otherwise, we can get "Parameter is not valid" exception if binary is corrupted for some reasons
                                stream.Seek(0, SeekOrigin.Begin);
                                b = new Bitmap(stream);
                            }
                            catch (ArgumentException exc)
                            {
                                exc.ToString();
                            }
                            //catch
                            //{
                            //    throw new ArgumentNullException("Error generating picture thumb. ID=" + picture.ID);
                            //}
                            if (b == null)
                            {
                                //bitmap could not be loaded for some reasons
                                return url;
                            }
                            var newSize = CalculateDimensions(b.Size, targetSize);

                            if (newSize.Width < 1)
                                newSize.Width = 1;
                            if (newSize.Height < 1)
                                newSize.Height = 1;

                            using (var newBitMap = new Bitmap(newSize.Width, newSize.Height))
                            {
                                using (var g = Graphics.FromImage(newBitMap))
                                {
                                    g.SmoothingMode = SmoothingMode.HighQuality;
                                    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                                    g.CompositingQuality = CompositingQuality.HighQuality;
                                    g.PixelOffsetMode = PixelOffsetMode.HighQuality;
                                    g.DrawImage(b, 0, 0, newSize.Width, newSize.Height);
                                    var ep = new EncoderParameters();
                                    ep.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, Convert.ToInt32(Setting.GetSettingValue("DefaultImageQuality")));
                                    ImageCodecInfo ici = GetImageCodecInfoFromExtension(lastPart);
                                    if (ici == null)
                                        ici = GetImageCodecInfoFromMimeType("image/jpeg");
                                    newBitMap.Save(thumbFilePath, ici, ep);
                                }
                            }
                            b.Dispose();
                        }
                    }
                }
            }
            url = GetThumbUrl(thumbFileName, storeLocation);
            return url;
        }

        /// <summary>
        /// Get a picture local path
        /// </summary>
        /// <param name="picture">Picture instance</param>
        /// <param name="targetSize">The target picture size (longest side)</param>
        /// <param name="showDefaultPicture">A value indicating whether the default picture is shown</param>
        /// <returns></returns>
        public static string GetThumbLocalPath(Picture picture, int targetSize = 0, bool showDefaultPicture = true)
        {
            string url = GetPictureUrl(picture, targetSize, showDefaultPicture);
            if (String.IsNullOrEmpty(url))
                return String.Empty;
            else
                return GetThumbLocalPath(Path.GetFileName(url));
        }

        #endregion

        #region Method

        public bool Delete()
        {
            bool success = Picture.DeletePicture(this.ID);
            if (success)
                this.ID = 0;
            return success;
        }

        public int Insert()
        {
            return Picture.InsertPicture(this.PictureBinary, this.MimeType, this.SeoFilename, this.IsNew);
        }

        public bool Update()
        {
            return Picture.UpdatePicture(this.ID, this.PictureBinary, this.MimeType, this.SeoFilename, this.IsNew);
        }

        #endregion
    }
}
